package com.google.demo.Data.Controler;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Observable;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.json.JSONException;
import org.json.JSONObject;


import com.google.demo.Config;
import com.google.demo.ScanQRCodeApp;
import com.google.demo.Info.Params;
import com.google.demo.Util.ErrorCode;
import com.google.demo.Util.Util;
import com.google.demo.model.DataResult;
import com.google.demo.model.MethodParams;

import android.os.AsyncTask;
import android.util.Log;

public class DataHelper extends Observable implements IServerService {
	private static final String TAG = "DataHelper";
	private final int MAX_SIZE_POOL = 3;

	private volatile static DataHelper uniqueInstance;

	public static int TIME_OUT_CONNECTION = 20000;
	private Map<String, HttpTask> mPoolExcutingTask;
	private Queue<HttpTask> mWaitingTask;

	public static DataHelper getInstance() {
		if (uniqueInstance == null) {
			synchronized (DataHelper.class) {
				if (uniqueInstance == null) {
					uniqueInstance = new DataHelper();
				}
			}
		}
		return uniqueInstance;
	}

	@SuppressWarnings("serial")
	public DataHelper() {
		super();
		mPoolExcutingTask = new LinkedHashMap<String, DataHelper.HttpTask>(
				MAX_SIZE_POOL) {
			@Override
			protected boolean removeEldestEntry(
					java.util.Map.Entry<String, HttpTask> eldest) {
				if (size() > MAX_SIZE_POOL) {
					Log.e(TAG, "remove eldest task "
							+ eldest.getValue().getUrl()
							+ " current size is : " + size());
					eldest.getValue().disconect();
					return true;
				} else {
					return false;
				}
			}
		};
		mWaitingTask = new ConcurrentLinkedQueue<DataHelper.HttpTask>();
	}

	private class HttpTask extends AsyncTask<Void, Void, DataResult> {
		MethodParams mMethodParam;
		String mHttpUrl;
		Map<String, String> mDataPost;
		HttpURLConnection mHttpUrlConnection;

		public HttpTask(String url, MethodParams params) {
			mHttpUrl = url;
			mMethodParam = params;
		}

		public HttpTask(String url, MethodParams params,
				Map<String, String> datapost) {
			mHttpUrl = url;
			mDataPost = datapost;
			mMethodParam = params;
		}

		public String getUrl() {
			return mHttpUrl;
		}

		public void disconect() {
			if (mHttpUrlConnection != null)
				mHttpUrlConnection.disconnect();
			cancel(true);
			mWaitingTask.add(new HttpTask(mHttpUrl, mMethodParam, mDataPost));
			Log.e(TAG, "waiting queue size: " + mWaitingTask.size());

		}
		@Override
		protected void onProgressUpdate(Void... values) {
			// TODO Auto-generated method stub
			super.onProgressUpdate(values);
		}
		@Override
		protected DataResult doInBackground(Void... params) {
			if(!Util.isNetworkAvailable())
			{
				DataResult result = new DataResult();
				result.setErrorCode(ErrorCode.NETWORK_ERROR);
				result.setMethodParam(mMethodParam);
				return result;
			}
			try {
				URL u = new URL(mHttpUrl);
				mHttpUrlConnection = (HttpURLConnection) u.openConnection();
				mHttpUrlConnection.setConnectTimeout(TIME_OUT_CONNECTION);
				mHttpUrlConnection.setReadTimeout(TIME_OUT_CONNECTION);
				mHttpUrlConnection.setAllowUserInteraction(false);
				mHttpUrlConnection.setDoInput(true);
				mHttpUrlConnection.setRequestProperty(Config.HEADER_APP_ID,
						Config.AppID);
				mHttpUrlConnection.setRequestProperty(Config.HEADER_VER,
						ScanQRCodeApp.Instance().getAppVersion());
				mHttpUrlConnection.setRequestProperty(Config.HEADER_DEVICE_ID,
						ScanQRCodeApp.Instance().getDeviceId());
				mHttpUrlConnection.setRequestProperty(
						Config.HEADER_DEVICE_TYPE, Config.DEVICE_TYPE);
//				mHttpUrlConnection.setRequestProperty(Config.HEADER_TOKEN,
//						ScanQRCodeApp.Instance().getLoginMobionInfo()
//								.getToken());
				mHttpUrlConnection.setRequestProperty(Config.HEADER_LANGUAGE,
						Locale.getDefault().getLanguage());
//				mHttpUrlConnection.setRequestProperty(Config.HEADER_SIGNATURE,
//						ScanQRCodeApp.Instance().getData("SIGNATURE").toString());
//				 mHttpUrlConnection.setRequestProperty(Config.HEADER_TOKEN,
//				 "d58cd2e0d9f011e1bd2d005056a70051");
				return request(mHttpUrlConnection, getParamsString(mDataPost),
						mMethodParam);
			} catch (FileNotFoundException e) {
				Log.e(TAG, "doInBackground " + e.toString());
				DataResult result = new DataResult();
				result.setErrorCode(ErrorCode.SERVER_ERROR);
				result.setMethodParam(mMethodParam);
				return result;
			} catch (IOException e) {
				Log.e(TAG, "doInBackground " + e.toString());
				DataResult result = new DataResult();
				result.setErrorCode(ErrorCode.TIME_OUT);
				result.setMethodParam(mMethodParam);
				return result;
			} finally {
				if (mHttpUrlConnection != null) {
					mHttpUrlConnection.disconnect();
				}
			}
		}

		@Override
		protected void onPostExecute(DataResult result) {

			mPoolExcutingTask.remove(mHttpUrl);
			if (mPoolExcutingTask.size() < MAX_SIZE_POOL) {
				HttpTask task = mWaitingTask.poll();
				if (task != null) {
					mPoolExcutingTask.put(task.getUrl(), task);
					task.execute((Void) null);
				}
			}

			if (result != null) {
				//isOnly4Cache = true: when call API but not notifyObservers to update
				if (mMethodParam.isOnly4Cache() == false) {
					setChanged();
					notifyObservers(result);
				}
			} else {
				Log.e(TAG, "http task result = null");
			}
		}
	}

	private DataResult request(HttpURLConnection conn, String postData,
			MethodParams methodParams) throws IOException {

		DataResult result;
		if (postData != null) {
			conn.setRequestMethod("POST");
			conn.setDoOutput(true);
			OutputStream out = new BufferedOutputStream(conn.getOutputStream());
			out.write(postData.getBytes());
			out.flush();
			out.close();
		} else {
			conn.setRequestMethod("GET");
		}

		int httpCode = conn.getResponseCode();
		if (httpCode == HttpURLConnection.HTTP_OK
				|| httpCode == HttpURLConnection.HTTP_CREATED) {
			InputStream in = new BufferedInputStream(conn.getInputStream());
			result = ParserDataHelper.getInstance().parse(in, methodParams);
			if (result == null)
				return null;

			result.setMethodParam(methodParams);
			return result;
		} else if(httpCode == HttpURLConnection.HTTP_CLIENT_TIMEOUT
				|| httpCode == HttpURLConnection.HTTP_GATEWAY_TIMEOUT) {
			Log.e("DATAHELPER REQUEST HTTP_CLIENT_TIMEOUT or HTTP_GATEWAY_TIMEOUT", httpCode + "");
			result = new DataResult();
			result.setMethodParam(methodParams);
			result.setErrorCode(ErrorCode.TIME_OUT);
		} else {
			Log.e("DATAHELPER REQUEST SERVER ERROR", httpCode + "");
			result = new DataResult();
			result.setMethodParam(methodParams);
			result.setErrorCode(ErrorCode.SERVER_ERROR);
		}
		return result;
	}

	public String ConvertStreamToString(InputStream is) {
		if (is != null) {
			Writer writer = new StringWriter();
			char[] buffer = new char[1024];
			try {
				Reader reader = new BufferedReader(new InputStreamReader(is,
						"UTF-8"));
				int n;
				while ((n = reader.read(buffer)) != -1) {
					writer.write(buffer, 0, n);
				}
			} catch (IOException e) {
				return "";
			}
			return writer.toString();
		} else {
			return "";
		}
	}

	@SuppressWarnings("unused")
	private void excute(String url, MethodParams methodparam) {
		if (mPoolExcutingTask.get(url) == null) {
			HttpTask task = new HttpTask(url, methodparam);
			mPoolExcutingTask.put(url, task);
			task.execute((Void) null);
		}
		Log.e(TAG, "place task to pool : " + url + " current size : "
				+ mPoolExcutingTask.size());
	}

	private void excute(String url, Map<String, String> data,
			MethodParams methodparam) {
		if (mPoolExcutingTask.get(url) == null) {
			HttpTask task = new HttpTask(url, methodparam, data);
			mPoolExcutingTask.put(url, task);
			task.execute((Void) null);
		}
		Log.e(TAG, "place task to pool : " + url + " current size : "
				+ mPoolExcutingTask.size());
	}

	private String getParamsString(Map<String, String> params) {
		if (params == null)
			return null;
		String ret = "";
		for (String key : params.keySet()) {
			try {
				String value = params.get(key);
				//ret += key + "=" + URLEncoder.encode(value, "UTF-8") + "&";
				ret += key + "=" + value + "&";
			} catch (Exception e) {
				Log.e(TAG, "EEEEEEEEEEEEEEEEXeption getParamsString_" + ret
						+ "_" + key);
			}
		}
		return ret.substring(0, ret.length() - 1);
	}

	@Override
	public void login(String username, String password, String devid) {
		// TODO Auto-generated method stub
		Map<String, String> params = new HashMap<String, String>();
		JSONObject jObject = new JSONObject();
		try {
			jObject.put(Params.USERNAME, username);
			jObject.put(Params.PASSWORD,password);
			jObject.put(Params.DEVICEID,devid);
			
		} catch (JSONException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		params.put("params", jObject.toString());
		excute(Urls.URL_POST_LOGIN, params, new MethodParams(Methods.login, 0, 0));
	}

	@Override
	public void postQRCode(String deviceId, String username, String sLat,
			String sLon, String sQRCode, int info1, int info2,
			int info3) {
		// TODO Auto-generated method stub
		Map<String, String> params = new HashMap<String, String>();
		JSONObject jObject = new JSONObject();
		try {
			jObject.put(Params.DEVICEID, deviceId);
			jObject.put(Params.USERNAME, username);
			jObject.put(Params.LAT, sLat);
			jObject.put(Params.LON, sLon);
			jObject.put(Params.QRCODE, sQRCode);
			jObject.put(Params.INFO1, info1);
			jObject.put(Params.INFO2, info2);
			jObject.put(Params.INFO3, info3);
		} catch (JSONException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		params.put("params", jObject.toString());
		excute(Urls.URL_POST_QRCODE, params, new MethodParams(Methods.postQRCode, 0, 0));
	}

	@Override
	public void postQRCodeOut(String deviceId, String username, String sLat,
			String sLon, String sQRCode, int info1, int info2, int info3) {
		// TODO Auto-generated method stub
		Map<String, String> params = new HashMap<String, String>();
		JSONObject jObject = new JSONObject();
		try {
			jObject.put(Params.DEVICEID, deviceId);
			jObject.put(Params.USERNAME, username);
			jObject.put(Params.LAT, sLat);
			jObject.put(Params.LON, sLon);
			jObject.put(Params.QRCODE, sQRCode);
			jObject.put(Params.INFO1, info1);
			jObject.put(Params.INFO2, info2);
			jObject.put(Params.INFO3, info3);
		} catch (JSONException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		params.put("params", jObject.toString());
		excute(Urls.URL_POST_QRCODE_OUT, params, new MethodParams(Methods.postQRCodeOut, 0, 0));
	}

	@Override
	public void postQRCodeIn(String deviceId, String username, String sLat,
			String sLon, String sQRCode, int info1, int info2, int info3) {
		// TODO Auto-generated method stub
		Map<String, String> params = new HashMap<String, String>();
		JSONObject jObject = new JSONObject();
		try {
			jObject.put(Params.DEVICEID, deviceId);
			jObject.put(Params.USERNAME, username);
			jObject.put(Params.LAT, sLat);
			jObject.put(Params.LON, sLon);
			jObject.put(Params.QRCODE, sQRCode);
			jObject.put(Params.INFO1, info1);
			jObject.put(Params.INFO2, info2);
			jObject.put(Params.INFO3, info3);
		} catch (JSONException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		params.put("params", jObject.toString());
		excute(Urls.URL_POST_QRCODE_IN, params, new MethodParams(Methods.postQRCodeIn, 0, 0));
	}
}
